//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-04-23     ZhangYihua   first version
//
// Description  : SPI read/write registers of off-chip ICs with specified frame format.
//################################################################################

module spi_rw_reg #(
parameter           CMD_LEN                 = 16,   // command bits length, include R/W(1bit) + Address(CMD_LEN-1 bits)
parameter           DATA_LEN                = 8,    // data bits length
parameter           RW_POS                  = 23,   // read/write indication bit position 
parameter           RD_IND                  = 1'b1, // read indication, if spi_frm[RW_POS]==RD_IND menas read.
parameter           CLK_DIV                 = 128,  // CLK_DIV>=2
parameter           CPOL                    = 1'b1, // SCK is 1'b0:LOW; 1'b1:HIGH; when idle
parameter           CPHA                    = 1'b1, // capture SDO/SDI at 1'b0:1st; 1'b1:2nd SCK edge
parameter           TURNROUND               = 0,    // read address to read data gap

// the following parameters are calculated automatically
parameter           FRM_LEN                 = CMD_LEN + DATA_LEN
) ( 
input                                       rst_n,
input                                       clk,

output                                      csn,
output  reg                                 sck,
output                                      sdo,
output  reg                                 sdoe,       // output enable for tristate
input                                       sdi,

input                                       spi_en,
input               [FRM_LEN-1:0]           spi_frm,       // cmd+wdata from CPU to sdo
input                                       spi_frm_fwe,   // forward write enable
output              [FRM_LEN-1:0]           spi_frm_bwd,   // rdata from sdi to CPU
output  reg                                 spi_frm_bwe,   // backward write enable
output  reg                                 spi_busy
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          CLK_DIV_BW              = $clog2(CLK_DIV);
localparam [CLK_DIV_BW-1:0] CLK_DIV_FULL    = CLK_DIV-1;
localparam [CLK_DIV_BW-1:0] CLK_DIV_HALF    = (CLK_DIV-1)/2;

localparam          CNT_FRM_BW              = $clog2(FRM_LEN);
localparam [CNT_FRM_BW-1:0] FRM_MAX         = FRM_LEN-1;
localparam [CNT_FRM_BW-1:0] FRM_TRN         = DATA_LEN;
localparam [CNT_FRM_BW-1:0] FRM_ZERO        = 0;

reg                                         spi_start;
reg                                         shift_en;
reg                 [CLK_DIV_BW-1:0]        cnt_div;
wire                                        half_en;
wire                                        full_en;
reg                 [CNT_FRM_BW-1:0]        cnt_frm;
wire                                        trn_ind;
reg                                         rdata_cap;
reg                 [FRM_LEN-1:0]           frm_shift;
wire                [CNT_FRM_BW-1:0]        tx_end_pos;

//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        spi_start <=`U_DLY 1'b0;
    end else begin
        spi_start <=`U_DLY spi_en & spi_frm_fwe & (~spi_busy);
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        shift_en <=`U_DLY 1'b0;
    end else begin
        if (spi_start==1'b1) begin
            shift_en <=`U_DLY 1'b1;
        end else if (full_en==1'b1 && cnt_frm==FRM_ZERO) begin
            shift_en <=`U_DLY 1'b0;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        spi_busy <=`U_DLY 1'b0;
    end else begin
        spi_busy <=`U_DLY spi_frm_fwe | spi_start | shift_en;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_div <=`U_DLY {CLK_DIV_BW{1'b0}};
    end else begin
        if (spi_start==1'b1 || cnt_div>=CLK_DIV_FULL) begin
            cnt_div <=`U_DLY {CLK_DIV_BW{1'b0}};
        end else if (shift_en==1'b1) begin
            cnt_div <=`U_DLY cnt_div + 1'd1;
        end
    end
end

assign half_en = (cnt_div==CLK_DIV_HALF) ? shift_en : 1'b0;
assign full_en = (cnt_div>=CLK_DIV_FULL) ? shift_en : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_frm <=`U_DLY {CNT_FRM_BW{1'b0}};
    end else begin
        if (spi_start==1'b1) begin
            cnt_frm <=`U_DLY FRM_MAX;
        end else if (full_en==1'b1 && trn_ind==1'b0) begin
            cnt_frm <=`U_DLY cnt_frm - 1'd1;
        end
    end
end

generate if (TURNROUND>0) begin:G_TR
    localparam          TRN_CW                  = $clog2(TURNROUND+1);
    localparam [TRN_CW-1:0]     TRN_MAX         = TURNROUND;

    reg                 [TRN_CW-1:0]            cnt_trn;

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cnt_trn <=`U_DLY {TRN_CW{1'b0}};
        end else if (half_en==1'b1) begin
            if (trn_ind==1'b1)
                cnt_trn <=`U_DLY cnt_trn - 1'd1;
            else if (spi_frm[RW_POS]==RD_IND && cnt_frm==FRM_TRN)
                cnt_trn <=`U_DLY TRN_MAX;
            else
                ;
        end
    end
    assign trn_ind = (cnt_trn>{TRN_CW{1'b0}}) ? 1'b1 : 1'b0;

end else begin:G_NTR
    assign trn_ind = 1'b0;
end endgenerate

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rdata_cap <=`U_DLY 1'b0;
    end else begin
        if (half_en==1'b1) begin
            rdata_cap <=`U_DLY sdi;
        end
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        frm_shift <=`U_DLY {FRM_LEN{1'b0}};
    end else begin
        if (spi_start==1'b1)
            frm_shift <=`U_DLY spi_frm;
        else if (full_en==1'b1 && trn_ind==1'b0) begin
            frm_shift <=`U_DLY {frm_shift[FRM_LEN-2:0], rdata_cap};
        end
    end
end

assign csn = ~shift_en;
assign sdo = frm_shift[FRM_LEN-1];

assign tx_end_pos = (spi_frm[RW_POS]==RD_IND) ? FRM_TRN :
                                                FRM_ZERO;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        sdoe <=`U_DLY 1'b0;
    end else begin
        if (spi_start==1'b1) begin
            sdoe <=`U_DLY 1'b1;
        end else if (full_en==1'b1 && cnt_frm==tx_end_pos) begin
            sdoe <=`U_DLY 1'b0;
        end else if (sdoe==1'b1 && shift_en==1'b0) begin
            sdoe <=`U_DLY 1'b0;
        end
    end
end

assign spi_frm_bwd = {spi_frm[DATA_LEN+:CMD_LEN], frm_shift[0+:DATA_LEN]};
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        spi_frm_bwe <=`U_DLY 1'b0;
    end else begin
        if (spi_frm[RW_POS]==RD_IND) begin
            spi_frm_bwe <=`U_DLY (cnt_frm==FRM_ZERO) ? full_en : 1'b0;
        end else begin
            spi_frm_bwe <=`U_DLY 1'b0;
        end
    end
end

generate if (CPHA==1'b0) begin:G_1st
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            sck <=`U_DLY CPOL;
        end else begin
            if (half_en==1'b1) begin
                sck <=`U_DLY ~CPOL;
            end else if (full_en==1'b1 && trn_ind==1'b0) begin
                sck <=`U_DLY CPOL;
            end
        end
    end
end else begin:G_2nd
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            sck <=`U_DLY CPOL;
        end else begin
            if (spi_start==1'b1 || (full_en==1'b1 && cnt_frm!=FRM_ZERO && trn_ind==1'b0)) begin
                sck <=`U_DLY ~CPOL;
            end else if (half_en==1'b1) begin
                sck <=`U_DLY CPOL;
            end
        end
    end
end endgenerate

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
